/**
 * 
 * The MIT License
 *  * Copyright (c) 2008 the original author or authors.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.googlecode.gchartjava;


/**
 * The Data class that contains the information that will be rendered in the
 * chart.
 * 
 * This class takes an array of floats from 0 to 100. If your data does not fall
 * in that range, you need to transform it so it does. This is the case for
 * negative number, for instance. If you are having trouble transforming or
 * normalizing your data please see {@link DataUtil}.
 * 
 * For the curious, behind the scenes, this class converts the float values to
 * the Google Chart API simple or extended encoding.
 * 
 * @author Julien Chastang (julien.c.chastang at gmail dot com)
 * 
 * @see DataUtil
 */
public class Data {
	
	private static final char[] extendedEncodingChars = { 'A', 'B', 'C', 'D',
			'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
			'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
			'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
			'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
			'4', '5', '6', '7', '8', '9', '-', '.' };
	
	private static final char[] simpleEncodingChars = { 'A', 'B', 'C', 'D',
			'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
			'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
			'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
			'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
			'4', '5', '6', '7', '8', '9'};

	private static final String[] extendedEncoding = new String[4096];
	
	private static final String[] simpleEncoding   = new String[62];

	static { // Fill the encoding arrays in static block.
		int cnt = 0;
		for (int i = 0; i < extendedEncodingChars.length; i++) {
			for (int j = 0; j < extendedEncodingChars.length; j++) {
				extendedEncoding[cnt++] = extendedEncodingChars[i] + ""
						+ extendedEncodingChars[j];
			}
		}
		cnt = 0;
		for (int i = 0; i < simpleEncodingChars.length; i++){
			simpleEncoding[cnt++] = simpleEncodingChars[i] + "";
		}
	}
	
	private final float[] data;
	
	private DataEncoding dataEncoding = DataEncoding.EXTENDED;

	/**
	 * The data that will eventually be rendered by a chart.
	 * 
	 * @param data
	 *            Float data. Float values must be between 0 and 100. data array
	 *            is deep copied so the array reference you pass is not kept
	 *            around.
	 */
	public Data(final float... data) {
		this.data = ArraysUtil.deepCopyFloatArray(data);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		 return (dataEncoding.equals(DataEncoding.SIMPLE)) ? simpleEncoding() : extendedEncoding(); 
	}

	/**
	 *
	 */
	private String extendedEncoding() {
		
		if (data == null || data.length == 0) {
			return "";
		}
		
		final StringBuilder encodedData = new StringBuilder();
		for (float datum : data) {
			int index = (datum >= 0f && datum <= 100f) ? Math.round((datum/100f) * (extendedEncoding.length-1)) : -1;
			encodedData.append((index < 0 || index > (extendedEncoding.length-1)) ? "__" : extendedEncoding[index]);
		}
		return encodedData.toString();
	}
	
	/**
	 * 
	 */
	private String simpleEncoding() {
		
		if (data == null || data.length == 0) {
			return "";
		}
		
		final StringBuilder encodedData = new StringBuilder();
		for (float datum : data) {
			int index = (datum >= 0f && datum <= 100f) ? Math.round((datum/100f) * (simpleEncoding.length-1)) : -1;
			encodedData.append((index < 0 || index > (simpleEncoding.length-1)) ? "_" : simpleEncoding[index]);
		}
		return encodedData.toString();
	}
	
	/**
	 * Set the data encoding.
	 */
	final void setDataEncoding(final DataEncoding dataEncoding) {
		this.dataEncoding = dataEncoding;
	}
	
	/**
	 * Get a deep copy of the backing array. Original backing array is not
	 * visible to client.
	 * 
	 * @return float array representing the data.
	 */
	public float[] getData() {
		return ArraysUtil.deepCopyFloatArray(data);
	}

	
	/**
	 * Get the size of the dataset.
	 * 
	 * @return Size of the dataset. 
	 */
	public int getSize(){
		return data.length;
	}
}
